/*
 * Projet Cryptolib CPS3 ASIP:
 *
 * Ensemble de fonctions utilitaires et interfaces avec la couche java du programme d'exemple
 *
*/
#include "javautil.h"
#include <stdlib.h>

/*
 * Fonction qui jète une Exception au niveau java
 *
 * @param env - Environnement JNI
 * @param message - Message de l'exception
 */
void throwException(JNIEnv *env, const char* message)
{
	jclass classeException;

	/* Récupération de la classe exception */
	classeException = (*env)->FindClass(env, CLASS_EXCEPTION);
	assert(classeException != 0);

	/* Instanciation et jet de l'exception */
	(*env)->ThrowNew(env, classeException, message);

}

/*
 * Fonction qui jète une PKCS11Exception au niveau java
 *
 * @param env - Environnement JNI
 * @param codeRetour - Code retour d'une fonction PKCS#11
 */
void throwPKCS11Exception(JNIEnv *env, CK_RV codeRetour)
{
	jclass classeException;
	jmethodID idConstructeur;
	jthrowable exceptionPKCS11;
	jlong codeErreur;

	/* Récupération du constructeur */
	classeException = (*env)->FindClass(env, CLASS_PKCS11EXCEPTION);
	assert(classeException != 0);
	idConstructeur = (*env)->GetMethodID(env, classeException, "<init>", "(J)V");
	assert(idConstructeur != 0);

	/* Instanciation et jet de l'exception */
	codeErreur = ckULongToJLong(codeRetour);
	exceptionPKCS11 = (jthrowable) (*env)->NewObject(env, classeException, idConstructeur, codeErreur);
	(*env)->Throw(env, exceptionPKCS11);

}

/*
 * Fonction qui jète une PKCS11Exception au niveau java si le code retour PKCS#11 est différent de CKR_OK
 *
 * @param env - Environnement JNI
 * @param codeRetour - Code retour d'une fonction PKCS#11
 */
void testCodeRetourOK(JNIEnv *env, CK_RV codeRetour)
{
	if (codeRetour != CKR_OK)
		throwPKCS11Exception(env,codeRetour);
}

/*
 * Fonction qui logue un message au niveau java
 * 
 * @param env - Environnement JNI
 * @param obj - Référence de l'objet qui contient la méthode log
 * @param niveau - Niveau de log à utiliser pour logger le message
 * @param jmessage - Le message à logger
 */
void log(JNIEnv *env, jobject obj, int niveau, jstring jmessage) {

	jmethodID idMethodeLog;
	jclass classe;
	
	/* Récupération de l'identifiant de la méthode */
	classe = (*env)->GetObjectClass(env, obj);
	idMethodeLog = (*env)->GetMethodID(env, classe, "log", "(ILjava/lang/String;)V");
	if (idMethodeLog == NULL) {
		throwException(env,"Methode log inexistante");
	}

	/* Invocation de la méthode de log pour logger le message */
	(*env)->CallVoidMethod(env, obj, idMethodeLog, niveau, jmessage);

}

/*
 * Fonction invoquant la demande de connexion d'un lecteur
 * 
 * @param env - Environnement JNI
 * @param obj - Référence de l'objet
 * @return - Le choix utilisateur (-1 pour Annulation / 1 pour Relancer)
 */
jint demandeConnexionLecteur(JNIEnv *env, jobject obj) {

	jmethodID idMethodeDemandeConnexionLecteur;
	jclass classe;

	/* Récupération de l'identifiant de la méthode */
	classe = (*env)->GetObjectClass(env, obj);
	idMethodeDemandeConnexionLecteur = (*env)->GetMethodID(env, classe, "demandeConnexionLecteur", "()I");
	if (idMethodeDemandeConnexionLecteur == NULL) {
		throwException(env,"Methode demandeConnexionLecteur inexistante");
	}

	/* Retourne le choix de l'utilisateur */
	return (*env)->CallIntMethod(env, obj, idMethodeDemandeConnexionLecteur);

}

/*
 * Fonction invoquant la demande d'insertion d'une carte supportée
 * 
 * @param env - Environnement JNI
 * @param obj - Référence de l'objet
 * @return - Le choix utilisateur (-1 pour Annulation / 1 pour Relancer)
 */
jint demandeInsertionCarte(JNIEnv *env, jobject obj) {

	jmethodID idMethodeIntroductionCarte;
	jclass classe;

	/* Récupération de l'identifiant de la méthode */
	classe = (*env)->GetObjectClass(env, obj);
	idMethodeIntroductionCarte = (*env)->GetMethodID(env, classe, "demandeInsertionCarte", "()I");
	if (idMethodeIntroductionCarte == NULL) {
		throwException(env,"Methode demandeInsertionCarte inexistante");
	}

	/* Retourne le choix de l'utilisateur */
	return (*env)->CallIntMethod(env, obj, idMethodeIntroductionCarte);

}

/*
 * Fonction invoquant la demande de choix du nombre d'essais possibles avant blocage du code porteur
 * 
 * @param env - Environnement JNI
 * @param obj - Référence de l'objet
 * @return - Le choix utilisateur (-1 pour Annulation / 1 pour N tentatives / 2 pour 3 tentatives)
 */
jint demandeChoixNbEssaisPossiblesAvantBlocageCodePorteur(JNIEnv *env, jobject obj) {

	jmethodID idDemandeChoixNbEssaisPossiblesAvantBlocageCodePorteur;
	jclass classe;

	/* Récupération de l'identifiant de la méthode */
	classe = (*env)->GetObjectClass(env, obj);
	idDemandeChoixNbEssaisPossiblesAvantBlocageCodePorteur = (*env)->GetMethodID(env, classe, "demandeChoixNbEssaisPossiblesAvantBlocageCodePorteur", "()I");
	if (idDemandeChoixNbEssaisPossiblesAvantBlocageCodePorteur == NULL) {
		throwException(env,"Methode demandeChoixNbEssaisPossiblesAvantBlocageCodePorteur inexistante");
	}

	/* Retourne le choix de l'utilisateur */
	return (*env)->CallIntMethod(env, obj, idDemandeChoixNbEssaisPossiblesAvantBlocageCodePorteur);

}

/*
 * Fonction invoquant la demande de saisie de code porteur
 * 
 * @param env - Environnement JNI
 * @param obj - Référence de l'objet
 * @param nbEssais - Flag permettant de déterminer le message à afficher concernant le nombre d'essai
 * @param conformiteCodePorteur - Etat de conformité du code porteur
 * @param code - Code porteur saisi
 * @param numeroSerieCarte - Numéro de série de la carte
 * @return - Le choix utilisateur (-1 pour Annulation / 1 pour Valider)
 */
jint demandeSaisieCodePorteur(JNIEnv *env, jobject obj, CK_FLAGS nbEssais, CK_FLAGS conformiteCodePorteur, char** code, unsigned char numeroSerieCarte[16]) {

	jmethodID idMethodeSaisieCodePorteur;
	jclass classe;
	jobjectArray saisieCodeInfos;
	jcharArray jNumeroSerieCarte = NULL;

	/* Récupération de l'identifiant de la méthode */
	classe = (*env)->GetObjectClass(env, obj);
	idMethodeSaisieCodePorteur = (*env)->GetMethodID(env, classe, "demandeSaisieCodePorteur", "(JJ[C)[Ljava/lang/Object;");
	if (idMethodeSaisieCodePorteur == NULL) {
		throwException(env,"Methode demandeSaisieCodePorteur inexistante");
	}

	if(numeroSerieCarte != NULL)
		jNumeroSerieCarte = cUnsignedCharToJCharArray(env, numeroSerieCarte ,16);

	saisieCodeInfos = (jobjectArray)(*env)->CallObjectMethod(env, obj, idMethodeSaisieCodePorteur, ckULongToJLong(nbEssais), ckULongToJLong(conformiteCodePorteur), jNumeroSerieCarte);

	/* Affectation du code porteur */
	*code = jCharArrayToCharPtr(env, (jcharArray)(*env)->GetObjectArrayElement(env, saisieCodeInfos, 1));

	/* Retourne le choix utilisateur */
	return extraitInt(env,(*env)->GetObjectArrayElement(env, saisieCodeInfos, 0));

}

/* Fonction invoquant la demande de recyclage du code porteur
 * 
 * @param env - Environnement JNI
 * @param obj - Référence de l'objet
 * @param nbEssais - Flag permettant de déterminer le message à afficher concernant le nombre d'essai
 * @param conformiteCodeDeblocage - Etat de conformité du code de déblocage
 * @param conformiteCodePorteur - Etat de conformité du code porteur
 * @param codeDeblocage - Code de déblocage saisi
 * @param nouveauCodePorteur1 - 1er nouveau code porteur saisi
 * @param nouveauCodePorteur2 - 2eme nouveau code porteur saisi
 * @param numeroSerieCarte - Numéro de série de la carte
 * @return - Le choix utilisateur (-1 pour Annulation / 1 pour Valider)
 */
jint demandeRecyclageCodePorteur(JNIEnv *env, jobject obj, CK_FLAGS nbEssais, CK_FLAGS conformiteCodeDeblocage, CK_FLAGS conformiteCodePorteur, char** codeDeblocage, char** nouveauCodePorteur1, char** nouveauCodePorteur2, unsigned char numeroSerieCarte[16]) {

	jmethodID idMethodeRecyclageCodePorteur;
	jclass classe;
	jobjectArray saisieCodeInfos;
	jcharArray jNumeroSerieCarte = NULL;

	/* Récupération de l'identifiant de la méthode */
	classe = (*env)->GetObjectClass(env, obj);
	idMethodeRecyclageCodePorteur = (*env)->GetMethodID(env, classe, "demandeRecyclageCodePorteur", "(JJJ[C)[Ljava/lang/Object;");
	if (idMethodeRecyclageCodePorteur == NULL) {
		throwException(env,"Methode demandeRecyclageCodePorteur inexistante");
	}

	if(numeroSerieCarte != NULL)
		jNumeroSerieCarte = cUnsignedCharToJCharArray(env, numeroSerieCarte ,16);

	saisieCodeInfos = (jobjectArray)(*env)->CallObjectMethod(env, obj, idMethodeRecyclageCodePorteur, ckULongToJLong(nbEssais), ckULongToJLong(conformiteCodeDeblocage), ckULongToJLong(conformiteCodePorteur), jNumeroSerieCarte);

	/* Affectation du code porteur */
	*codeDeblocage = jCharArrayToCharPtr(env, (jcharArray)(*env)->GetObjectArrayElement(env, saisieCodeInfos, 1));
	*nouveauCodePorteur1 = jCharArrayToCharPtr(env, (jcharArray)(*env)->GetObjectArrayElement(env, saisieCodeInfos, 2));
	*nouveauCodePorteur2 = jCharArrayToCharPtr(env, (jcharArray)(*env)->GetObjectArrayElement(env, saisieCodeInfos, 3));

	/* Retourne le choix utilisateur */
	return extraitInt(env,(*env)->GetObjectArrayElement(env, saisieCodeInfos, 0));

}

/*
 * Fonction invoquant la demande d'action carte ou lecteur
 * Actions possibles:
 *
 * TYPE_ACTION_RETRAIT_CARTE : Demande le retrait d'une carte
 * TYPE_ACTION_INSERTION_CARTE : Demande l'insertion d'une carte précédemment retiré d'un lecteur
 * TYPE_ACTION_RETRAIT_LECTEUR : Demande le retrait d'un lecteur
 * TYPE_ACTION_CONNEXION_LECTEUR : Demande la reconnexion du lecteur avec la carte qu'il contenait ou l'insertion de cette carte dans un autre lecteur
 *
 * @param env - Environnement JNI
 * @param obj - Référence de l'objet qui contient la méthode log
 * @param typeActionAttendue - Le type d'évènement
 * @param numeroSerieCarte - Numéro de série de la carte
 * @return - Le choix utilisateur (-1 pour Annulation / 1 Valider)
 */
jint demandeActionSurLecteurCarte(JNIEnv *env, jobject obj, int typeActionAttendue, unsigned char numeroSerieCarte[16]) {

	jmethodID idDemandeActionLecteurCarte;
	jclass classe;
	jcharArray jNumeroSerieCarte = NULL;

	/* Récupération de l'identifiant de la méthode */
	classe = (*env)->GetObjectClass(env, obj);
	idDemandeActionLecteurCarte = (*env)->GetMethodID(env, classe, "demandeActionLecteurCarte", "(I[C)I");
	if (idDemandeActionLecteurCarte == NULL) {
		throwException(env,"Methode demandeActionLecteurCarte inexistante");
	}

	if(numeroSerieCarte != NULL)
		jNumeroSerieCarte = cUnsignedCharToJCharArray(env, numeroSerieCarte ,16);

	/* Retourne le choix de l'utilisateur */
	return (*env)->CallIntMethod(env, obj, idDemandeActionLecteurCarte, typeActionAttendue, jNumeroSerieCarte);

}

/*
 * Fonction invoquant l'alerte de carte retrouvée
 * 
 * @param env - Environnement JNI
 * @param obj - Référence de l'objet
 */
void alerteCarteRetrouvee(JNIEnv *env, jobject obj) {

	jmethodID idMethodeAlerteCarteRetrouvee;
	jclass classe;

	/* Récupération de l'identifiant de la méthode */
	classe = (*env)->GetObjectClass(env, obj);
	idMethodeAlerteCarteRetrouvee = (*env)->GetMethodID(env, classe, "alerteCarteRetrouvee", "()V");
	if (idMethodeAlerteCarteRetrouvee == NULL) {
		throwException(env,"Methode alerteCarteRetrouvee inexistante");
	}

	(*env)->CallVoidMethod(env, obj, idMethodeAlerteCarteRetrouvee);

}


/* Fonction invoquant la demande de saisie de modification de l'attribut d'un objet
 * 
 * @param env - Environnement JNI
 * @param obj - Référence de l'objet
 * @param valeur - La valeur de l'attribut de l'objet
 * @param tailleValeur - Taille de la valeur de l'attribut de l'objet
 * @param pValeurModifiee - Référence sur la valeur modifiée de l'attribut de l'objet
 * @param tailleValeurModifiee - Taille de la valeur modifiée de l'attribut de l'objet
 * @return - Le choix utilisateur (-1 pour Annulation / 1 pour Valider)
 */
jint demandeSaisieModificationObjet(JNIEnv *env, jobject obj, CK_BYTE_PTR valeur, CK_ULONG tailleValeur, CK_BYTE_PTR *pValeurModifiee, CK_ULONG_PTR tailleValeurModifiee) {

	jmethodID idMethodeModificationObjet;
	jclass classe;
	jbyteArray jValeur = NULL;
	jobjectArray saisieValeur;

	/* Récupération de l'identifiant de la méthode */
	classe = (*env)->GetObjectClass(env, obj);
	idMethodeModificationObjet = (*env)->GetMethodID(env, classe, "demandeModificationObjetDonneesApplicatives", "([B)[Ljava/lang/Object;");
	if (idMethodeModificationObjet == NULL) {
		throwException(env,"Methode demandeModificationObjet inexistante");
	}

	jValeur = ckByteArrayToJByteArray(env, valeur, tailleValeur);
	saisieValeur = (jobjectArray)(*env)->CallObjectMethod(env, obj, idMethodeModificationObjet, jValeur);

	/* Affectation de la valeur de l'objet */
	jByteArrayToCKByteArray(env, (jbyteArray)(*env)->GetObjectArrayElement(env, saisieValeur, 1), pValeurModifiee, tailleValeurModifiee);

	/* Retourne le choix utilisateur */
	return extraitInt(env,(*env)->GetObjectArrayElement(env, saisieValeur, 0));

}


/* 
 * Fonction de conversion d'un jchararray en const char*
 * 
 * @param env - Environnement JNI
 * @param jcharTab - Le jchararray à convertir
 * @return - Le jcharArray
 */
char* jCharArrayToCharPtr(JNIEnv *env, jcharArray jcharTab) {

	jint taille;
	char* ptrCharTab = NULL;
	jchar* ptrJChar = NULL;
	jboolean isCopy;
	int i;

	if(jcharTab != NULL) {

		/* Allocation du char* */
		taille = (jint)(*env)->GetArrayLength(env,jcharTab);
		ptrCharTab = calloc(taille+1,sizeof(char));
		if(ptrCharTab == NULL)
			return NULL;

		/* Recopie du jchararray dans le char* */
		ptrJChar =  (*env)->GetCharArrayElements(env,jcharTab, &isCopy);
		// Boucle sur le tableau et transforme chaque caractère en caractère C en castant de jchar à char
		for(i = 0; i < taille; i++)
			ptrCharTab[i] = (char)ptrJChar[i];

		/* Desalloue la copie temporaire */
		if(isCopy == JNI_TRUE)
		 (*env)->ReleaseCharArrayElements(env,jcharTab, ptrJChar, 0);
	
	}

	return ptrCharTab;
}

/* 
 * Fonction de conversion d'un const char* en jstring
 * 
 * @param env - Environnement JNI
 * @param chaine - La chaine à convertir en jstring
 * @return - le jstring
 */
jstring cCharPtrTojstring(JNIEnv *env, const char* chaine) {

	return (*env)->NewStringUTF(env,chaine);

}

/* 
 * Fonction d'extraction d'un int depuis un objet
 * 
 * @param env - Environnement JNI
 * @param chaine - L'objet à extraire
 * @return - le jint
 */
jint extraitInt(JNIEnv *env, jobject obj) 
{ 
	jmethodID idMethodeIntValue;
	jclass classe;

	classe = (*env)->GetObjectClass(env, obj);
	idMethodeIntValue = (*env)->GetMethodID(env, classe, "intValue", "()I");
	if (idMethodeIntValue == NULL) {
		throwException(env,"Methode intValue inexistante");
	}

	return (*env)->CallIntMethod(env, obj, idMethodeIntValue);

}

/*
 * Fonction de conversion d'un const unsigned char[] et sa longueur en jcharArray
 *
 * @param env - Environnement JNI
 * @param chaine - Chaîne à convertir
 * @param tailleChaine - Taille de la chaîne à convertir
 * @return - le jcharArray
 */
jcharArray cUnsignedCharToJCharArray(JNIEnv *env, const unsigned char chaine[], int tailleChaine)
{
	int i;
	jchar* jpTempChaine;
	jcharArray jChaine;

	jpTempChaine = (jchar*) malloc(tailleChaine * sizeof(jchar));
	for (i=0; i<tailleChaine; i++) {
		jpTempChaine[i] = cUnsignedCharToJChar(chaine[i]);
	}
	jChaine = (*env)->NewCharArray(env,(jsize) tailleChaine);
	(*env)->SetCharArrayRegion(env, jChaine, 0, (jsize) tailleChaine, jpTempChaine);
	free(jpTempChaine);

	return jChaine;
}

/*
 * Convertit un tableau de CK_BYTE et sa longueur en jbyteArray.
 *
 * @param env - Environnement JNI
 * @param pTableau - Pointeur sur le tableau de CK_BYTE à convertir
 * @param taille - Taille du tableau à convertir
 * @return - Le jbyteArray
 */
jbyteArray ckByteArrayToJByteArray(JNIEnv *env, const CK_BYTE_PTR pTableau, CK_ULONG taille)
{
	CK_ULONG i;
	jbyte* jpTemp;
	jbyteArray jTableau;

	/* Si le CK_BYTE a la même longueur que le jbyte, on sauvegarde une copie additionnelle */
	if (sizeof(CK_BYTE) == sizeof(jbyte))
		jpTemp = (jbyte*) pTableau;
	else {
		jpTemp = (jbyte*) malloc((taille) * sizeof(jbyte));
		for (i=0; i<taille; i++)
			jpTemp[i] = ckByteToJByte(pTableau[i]);
	}

	jTableau = (*env)->NewByteArray(env, ckULongToJSize(taille));
	(*env)->SetByteArrayRegion(env, jTableau, 0, ckULongToJSize(taille), jpTemp);

	if (sizeof(CK_BYTE) != sizeof(jbyte))
		free(jpTemp);

	return jTableau;
}

/*
 * Convertit un jbyteArray en tableau de CK_BYTE. La mémoire allouée doit être libérée après utilisation
 *
 * @param env - Environnement JNI
 * @param jTableau - le jbyteArray à convertir
 * @param pTableau - la référence, où le pointeur vers le nouveau tableau de CK_BYTE sera stocké
 * @param tailleTableau - Taille du tableau
 */
void jByteArrayToCKByteArray(JNIEnv *env, const jbyteArray jTableau, CK_BYTE_PTR *pTableau, CK_ULONG_PTR tailleTableau)
{
	jbyte* jpTemp;
	CK_ULONG i;

	if(jTableau == NULL) {
		*pTableau = NULL_PTR;
		*tailleTableau = 0L;
		return;
	}
	*tailleTableau = (*env)->GetArrayLength(env, jTableau);
	jpTemp = (jbyte*) malloc((*tailleTableau) * sizeof(jbyte));
	(*env)->GetByteArrayRegion(env, jTableau, 0, *tailleTableau, jpTemp);

  /* Si CK_BYTE est de la même taille que jbyte, on sauvegarde une copie additionnelle */
  if (sizeof(CK_BYTE) == sizeof(jbyte)) {
    *pTableau = (CK_BYTE_PTR) jpTemp;
  } else {
	  *pTableau = (CK_BYTE_PTR) malloc ((*tailleTableau) * sizeof(CK_BYTE));
	  for (i=0; i<(*tailleTableau); i++) {
		  (*pTableau)[i] = jByteToCKByte(jpTemp[i]);
	  }
	  free(jpTemp);
  }
}
